/* VIMApplication.m */

#import "../vim.h"
#import <Cocoa/Cocoa.h>
#import "VIMApplication.h"

@interface NSColor (MMProtocol)
+ (NSColor *) colorWithRgbInt:(int)rgb;
@end

@implementation VIMApplication

- (id)init
{
    if ((self = [super init])) {
        drawData = [[NSMutableData alloc] initWithCapacity: 1024];
    }

    return self;
}

- (void)dealloc
{
    [drawData release];
    [super dealloc];
}

- (void) stop:(id)sender
{
     NSEvent *event;
     NSAutoreleasePool *pool;
     [super stop: sender];
     
     pool = [[NSAutoreleasePool alloc] init];
     
     /* NOTE: Because just call -stop: can not jump out the
      * [NSApp run] loop, we have to manully click some where 
      * or press a key. Here we use this trick (found in VLC) 
      * to send a dummy event to break out of the event loop */
     event = [NSEvent mouseEventWithType: NSLeftMouseUp
                 location: NSMakePoint( 0, 0 ) modifierFlags: 0
                 timestamp: 1 windowNumber: [[NSApp mainWindow] windowNumber]
                 context: [NSGraphicsContext currentContext] eventNumber: 1
                 clickCount: 1 pressure: 0.0];
     [NSApp postEvent: event atStart: YES];
     
     [pool release];
}

- (void) initializeApplicationTimer:(NSTimer *)timer
{
    [NSApp stop: self];
}

- (void) applicationDidFinishLaunching:(NSNotification *)aNotification
{
    NSLog(@"applicationDidFinishLaunching");
}

- (void) menuAction:(id)sender
{
    NSMenuItem *item = (NSMenuItem *) sender;
    vimmenu_T *menu;
    
    /* NSMenuItem does not have a specifically
     * made "user data" to contain vimmenu_T, 
     * so we have to use this trick, cast an
     * int to a pointer, hopefully in all Macs
     * they are in the same length */
    if ((menu = (vimmenu_T *)[item tag]) == NULL)
        return;

    if (menu->cb != NULL)
        gui_menu_cb(menu);
}

- (void) applicationWillTerminate:(NSNotification *)aNotification
{
}

- (void) createApplicationMenu
{
    NSMenu *appleMenu, *services;
    NSMenuItem *menuItem;
    NSString *title;
    NSString *appName;
    
    [NSApp setMainMenu: [NSMenu new]];
    
    appName = @"Vim";
    appleMenu = [[NSMenu alloc] initWithTitle: @""];

    /* Add menu items */
    title = [[NSString alloc] initWithFormat: @"About %@", appName];
    [appleMenu addItemWithTitle: title 
                         action: @selector(orderFrontStandardAboutPanel:) 
                  keyEquivalent: @""];
    [title release];

    [appleMenu addItem: [NSMenuItem separatorItem]];

    // Services Menu
    services = [[[NSMenu alloc] init] autorelease];
    [appleMenu addItemWithTitle: @"Services" 
                         action: nil
                  keyEquivalent: @""];
    [appleMenu setSubmenu: services forItem: [appleMenu itemWithTitle: @"Services"]];

    // Hide AppName
    title = [[NSString alloc] initWithFormat: @"Hide %@", appName];
    [appleMenu addItemWithTitle: title action: @selector(hide:) keyEquivalent: @"h"];
    [title release];

    // Hide Others
    menuItem = (NSMenuItem *)[appleMenu addItemWithTitle: @"Hide Others" 
                                                  action: @selector(hideOtherApplications:) 
                                           keyEquivalent: @"h"];
    [menuItem setKeyEquivalentModifierMask: (NSAlternateKeyMask | NSCommandKeyMask)];

    // Show All
    [appleMenu addItemWithTitle: @"Show All" 
                         action: @selector(unhideAllApplications:) 
                  keyEquivalent: @""];

    [appleMenu addItem: [NSMenuItem separatorItem]];

    // Quit AppName
    title = [[NSString alloc] initWithFormat: @"Quit %@", appName];
    [appleMenu addItemWithTitle: title 
                         action: @selector(terminate:) 
                  keyEquivalent: @"q"];
    [title release];

    /* Put menu into the menubar */
    menuItem = [[NSMenuItem alloc] initWithTitle: @"" 
                                          action: nil 
                                   keyEquivalent: @""];
    [menuItem setSubmenu: appleMenu];
    [[NSApp mainMenu] addItem: menuItem];

    /* Tell the application object that this is now the application menu */
    [NSApp setAppleMenu: appleMenu];
    [NSApp setServicesMenu: services];

    /* Finally give up our references to the objects */
    [appleMenu release];
    [menuItem release];    
}

- (void) createWindow
{
    NSWindow    *window;
    unsigned int style;
    NSRect       rect = NSMakeRect(0, 0, 800, 600);

    style = NSTitledWindowMask | 
    NSMiniaturizableWindowMask | 
          NSClosableWindowMask | 
         NSResizableWindowMask;

    window = [[NSWindow alloc] initWithContentRect: rect 
                                         styleMask: style
                                           backing: NSBackingStoreBuffered 
                                             defer: NO];

    // [window setBackgroundColor: [NSColor clearColor]];
    // [window setOpaque: YES];
    [window center];
    [window setViewsNeedDisplay: NO];
    [window setTitle: @"gVIM on Macintosh"];
    [window makeKeyAndOrderFront: self];
    [window setResizeIncrements: NSMakeSize(gui.char_width, gui.char_height)];
    
    NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
    NSTextContainer *textContainer = [[NSTextContainer alloc] initWithContainerSize: 
        NSMakeSize(1.0e7, 1.0e7)];

    [textContainer setWidthTracksTextView: NO];
    [textContainer setHeightTracksTextView: NO];
    [textContainer setLineFragmentPadding: 0];

    textStorage = [[VIMTextStorage alloc] init];
    [textStorage setMaxRows: gui.num_rows
                    columns: gui.num_cols];

    [textStorage addLayoutManager: layoutManager];
    [layoutManager addTextContainer: textContainer];

    contentView = [[VIMTextView alloc] initWithFrame: rect
                                       textContainer: textContainer];
    [[contentView layoutManager] setDelegate: self];
    
    [textContainer release];
    [layoutManager release];

    [self fitWindowToTextStorage];

    [[window contentView] addSubview: contentView];
    [window makeFirstResponder: contentView];
    [NSApp activateIgnoringOtherApps: YES];

    return;
}

- (void) setRunning:(BOOL)isRunning
{
    running = isRunning;
}

- (BOOL) running
{
    return running;
}

- (VIMTextView *) contentView
{
    return contentView;
}

- (VIMTextStorage *) textStorage
{
    return textStorage;
}

- (NSSize) contentSizeForTextViewSize:(NSSize)textViewSize
{
    NSSize size = textViewSize;

    return size;
}

- (NSRect)textViewRectForContentSize:(NSSize)contentSize
{
    NSRect rect = NSMakeRect(0, 0, contentSize.width, contentSize.height);

    return rect;
}

- (void) fitWindowToTextStorage
{
    NSWindow *win = [self keyWindow];
    NSRect frame = [win frame];
    NSRect contentRect = [win contentRectForFrameRect: frame];
    NSSize newSize = [self contentSizeForTextViewSize: [textStorage size]];

    // Keep top-left corner of the window fixed when resizing.
    contentRect.origin.y -= newSize.height - contentRect.size.height;
    contentRect.size = newSize;

    frame = [win frameRectForContentRect: contentRect];
    NSRect maxFrame = [win constrainFrameRect: frame toScreen: [win screen]];

    // HACK!  Assuming the window frame cannot already be placed too high,
    // adjust 'maxFrame' so that it at least as high up as the current frame.
    // The reason for doing this is that constrainFrameRect:toScreen: does not
    // always seem to utilize as much area as possible.
    if (NSMaxY(frame) > NSMaxY(maxFrame))
    {
        maxFrame.size.height = frame.origin.y - maxFrame.origin.y
                + frame.size.height;
    }

    if (NSEqualRects(maxFrame, frame)) {
        // The new window frame fits on the screen, so resize the window.
        [win setFrame: frame display: YES];
    } else {
        // The new window frame is too big to fit on the screen, so fit the
        // text storage to the biggest frame which will fit on the screen.
        //NSLog(@"Proposed window frame does not fit on the screen!");

        [win setFrame: [self fitWindowToFrame: maxFrame] 
              display: YES];
    }
}

- (NSRect) fitWindowToFrame:(NSRect)frame
{
    NSWindow *win = [self keyWindow];
    NSRect contentRect = [win contentRectForFrameRect: frame];
    NSRect textViewRect = [self textViewRectForContentSize: contentRect.size];
    NSSize fitSize = [textStorage fitToSize: textViewRect.size];
    NSSize newSize = [self contentSizeForTextViewSize: fitSize];

    // Keep top-left corner of 'frame' fixed.
    contentRect.origin.y -= newSize.height - contentRect.size.height;
    contentRect.size = newSize;

    return [win frameRectForContentRect: contentRect];
}

- (void) performBatchDrawWithData:(NSData *)data
{
    if (! textStorage)
        return;

    const void *bytes = [data bytes];
    const void *end = bytes + [data length];

    [textStorage beginEditing];

    while (bytes < end)
    {
        int type = *((int*)bytes);  bytes += sizeof(int);
        int rows = *((int*)bytes);  bytes += sizeof(int);
        int cols = *((int*)bytes);  bytes += sizeof(int);

        if (ClearAllDrawType != type)
        {
            // All draw types except clear all rely on the text storage have
            // the appropriate dimensions, so set it before attempting to
            // modify the text storage.
            [textStorage setMaxRows: rows columns: cols];
        }

        if (ClearAllDrawType == type)
        {
            int color = *((int*)bytes);  bytes += sizeof(int);

            [textStorage clearAllWithColor: [NSColor colorWithRgbInt: color]];
        } else if (ClearBlockDrawType == type)
        {
            int color = *((int*)bytes);  bytes += sizeof(int);
            int row1 = *((int*)bytes);  bytes += sizeof(int);
            int col1 = *((int*)bytes);  bytes += sizeof(int);
            int row2 = *((int*)bytes);  bytes += sizeof(int);
            int col2 = *((int*)bytes);  bytes += sizeof(int);

            [textStorage clearBlockFromRow: row1 
                                    column: col1
                                     toRow: row2 
                                    column: col2
                                     color: [NSColor colorWithRgbInt: color]];
        } else if (DeleteLinesDrawType == type)
        {
            int color = *((int*)bytes);  bytes += sizeof(int);
            int row = *((int*)bytes);  bytes += sizeof(int);
            int count = *((int*)bytes);  bytes += sizeof(int);
            int bot = *((int*)bytes);  bytes += sizeof(int);
            int left = *((int*)bytes);  bytes += sizeof(int);
            int right = *((int*)bytes);  bytes += sizeof(int);

            [textStorage deleteLinesFromRow: row 
                                  lineCount: count
                               scrollBottom: bot 
                                       left: left 
                                      right: right
                                      color: [NSColor colorWithRgbInt: color]];
        } else if (ReplaceStringDrawType == type)
        {
            int bg = *((int*)bytes);  bytes += sizeof(int);
            int fg = *((int*)bytes);  bytes += sizeof(int);
            int row = *((int*)bytes);  bytes += sizeof(int);
            int col = *((int*)bytes);  bytes += sizeof(int);
            int flags = *((int*)bytes);  bytes += sizeof(int);
            int len = *((int*)bytes);  bytes += sizeof(int);
            NSString *string = [[NSString alloc] initWithBytesNoCopy: (void*)bytes
                                                              length: len
                                                            encoding: NSUTF8StringEncoding
                                                        freeWhenDone: NO];
            bytes += len;

            [textStorage replaceString: string
                                 atRow: row column: col
                             withFlags: flags
                       foregroundColor: [NSColor colorWithRgbInt: fg]
                       backgroundColor: [NSColor colorWithRgbInt: bg]];

            [string release];
        } else if (InsertLinesDrawType == type)
        {
            int color = *((int*)bytes);  bytes += sizeof(int);
            int row = *((int*)bytes);  bytes += sizeof(int);
            int count = *((int*)bytes);  bytes += sizeof(int);
            int bot = *((int*)bytes);  bytes += sizeof(int);
            int left = *((int*)bytes);  bytes += sizeof(int);
            int right = *((int*)bytes);  bytes += sizeof(int);

            [textStorage insertLinesAtRow: row lineCount: count
                             scrollBottom: bot left: left right: right
                                    color: [NSColor colorWithRgbInt: color]];
        } else {
            NSLog(@"WARNING: Unknown draw type (type=%d)", type);
        }
    }

    [textStorage endEditing];
}

- (void) flushDrawing
{
    [self performBatchDrawWithData: drawData];
    [drawData setLength: 0];
}

- (void)clearAll
{
    int type = ClearAllDrawType;

    // Any draw commands in queue are effectively obsolete since this clearAll
    // will negate any effect they have, therefore we may as well clear the
    // draw queue.
    [drawData setLength:0];

    [drawData appendBytes:&type length:sizeof(int)];
    [drawData appendBytes:&Rows length:sizeof(int)];
    [drawData appendBytes:&Columns length:sizeof(int)];

    [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
}

- (void)clearBlockFromRow:(int)row1 column:(int)col1
                    toRow:(int)row2 column:(int)col2
{
    int type = ClearBlockDrawType;

    [drawData appendBytes:&type length:sizeof(int)];
    [drawData appendBytes:&Rows length:sizeof(int)];
    [drawData appendBytes:&Columns length:sizeof(int)];

    [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
    [drawData appendBytes:&row1 length:sizeof(int)];
    [drawData appendBytes:&col1 length:sizeof(int)];
    [drawData appendBytes:&row2 length:sizeof(int)];
    [drawData appendBytes:&col2 length:sizeof(int)];
}

- (void)deleteLinesFromRow:(int)row count:(int)count
              scrollBottom:(int)bottom left:(int)left right:(int)right
{
    int type = DeleteLinesDrawType;

    [drawData appendBytes:&type length:sizeof(int)];
    [drawData appendBytes:&Rows length:sizeof(int)];
    [drawData appendBytes:&Columns length:sizeof(int)];

    [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
    [drawData appendBytes:&row length:sizeof(int)];
    [drawData appendBytes:&count length:sizeof(int)];
    [drawData appendBytes:&bottom length:sizeof(int)];
    [drawData appendBytes:&left length:sizeof(int)];
    [drawData appendBytes:&right length:sizeof(int)];
}

- (void)replaceString:(char*)s length:(int)len row:(int)row column:(int)col
                flags:(int)flags
{
    int type = ReplaceStringDrawType;

    [drawData appendBytes:&type length:sizeof(int)];
    [drawData appendBytes:&Rows length:sizeof(int)];
    [drawData appendBytes:&Columns length:sizeof(int)];

    [drawData appendBytes:&backgroundColor length:sizeof(int)];
    [drawData appendBytes:&foregroundColor length:sizeof(int)];
    [drawData appendBytes:&row length:sizeof(int)];
    [drawData appendBytes:&col length:sizeof(int)];
    [drawData appendBytes:&flags length:sizeof(int)];
    [drawData appendBytes:&len length:sizeof(int)];
    [drawData appendBytes:s length:len];
}

- (void)insertLinesFromRow:(int)row count:(int)count
              scrollBottom:(int)bottom left:(int)left right:(int)right
{
    int type = InsertLinesDrawType;

    [drawData appendBytes:&type length:sizeof(int)];
    [drawData appendBytes:&Rows length:sizeof(int)];
    [drawData appendBytes:&Columns length:sizeof(int)];

    [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
    [drawData appendBytes:&row length:sizeof(int)];
    [drawData appendBytes:&count length:sizeof(int)];
    [drawData appendBytes:&bottom length:sizeof(int)];
    [drawData appendBytes:&left length:sizeof(int)];
    [drawData appendBytes:&right length:sizeof(int)];
}

- (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
{
    defaultBackgroundColor = bg;
    defaultForegroundColor = fg;

    [textStorage setDefaultColorsBackground: [NSColor colorWithRgbInt: bg]
                                 foreground: [NSColor colorWithRgbInt: fg]];
}

- (void) setBackgroundColor:(int)color
{
    backgroundColor = color;
}

- (void) setForegroundColor:(int)color
{
    foregroundColor = color;
}

@end

@implementation NSColor (MMProtocol)

+ (NSColor *)colorWithRgbInt:(int)rgb
{
    float r = ((rgb >> 16) & 0xff) / 255.0f;
    float g = ((rgb >> 8) & 0xff) / 255.0f;
    float b = (rgb & 0xff) / 255.0f;

    return [NSColor colorWithCalibratedRed: r 
                                     green: g 
                                      blue: b 
                                     alpha: 1.0f];
}

@end // NSColor (MMProtocol)
